Saltar al contenido principal

7.3.1 Introducción a SQLite

A menudo, los ficheros de texto o JSON se quedan cortos. Cuando necesitamos integridad, relaciones complejas entre datos y una búsqueda eficiente, entramos en el mundo de las Bases de Datos Relacionales (RDBMS).

SQLite es una base de datos extraordinaria: no necesita un servidor (es serverless), vive en un simple archivo y es el motor de base de datos más desplegado del mundo (está en tu móvil, en tu navegador y en casi cualquier software moderno).


1️⃣ El Concepto: Base de Datos Embebida​

A diferencia de MySQL o PostgreSQL, donde Python se conecta a un "servidor" externo, en SQLite Python es el motor de la base de datos.

  • Todo en un archivo: Tu base de datos es un único fichero .db o .sqlite.
  • Zero Config: No hay que instalar servicios ni configurar puertos.
  • Librería estándar: Python ya viene con el módulo sqlite3 de serie.
ojo

SQLite es perfecto para aplicaciones locales, herramientas de análisis y sitios web de tráfico medio. Para sistemas distribuidos masivos con cientos de escrituras simultáneas por segundo, se suelen preferir sistemas cliente-servidor.


2️⃣ El Ciclo de Vida de la Conexión​

Para trabajar con SQLite, seguimos un flujo riguroso: Conectar ➡️ Operar ➡️ Confirmar ➡️ Cerrar.

🟩 Apertura de la conexión​

La función sqlite3.connect() es nuestra puerta de entrada.

Conexión estándar
import sqlite3

# Crea el archivo si no existe
conexion = sqlite3.connect("mi_base_datos.db")

print("Conexión establecida con éxito")

# Siempre cerrar el recurso
conexion.close()

🟦 Bases de datos "efímeras" (en RAM)​

A veces solo necesitas una base de datos para pruebas rápidas o procesamiento temporal que no quieres guardar en disco.

# Se crea en la memoria RAM y desaparece al cerrar el programa
conexion = sqlite3.connect(":memory:")

🟨 El Cursor: tu espacio de trabajo​

La conexión es el "cable", pero el cursor es la "mano" que ejecuta las órdenes SQL.

cursor = conexion.cursor()

3️⃣ Creación de Estructuras (DDL)​

Antes de guardar datos, definimos la "forma" de nuestra información mediante tablas.

import sqlite3

with sqlite3.connect("inventario.db") as conn:
cursor = conn.cursor()

# Usamos triples comillas para SQL multilínea (más legible)
cursor.execute("""
CREATE TABLE IF NOT EXISTS productos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT NOT NULL,
precio REAL CHECK(precio >= 0),
disponible BOOLEAN DEFAULT 1
)
""")

# En operaciones DDL (CREATE), no suele ser obligatorio commit,
# pero es una buena práctica para asegurar persistencia inmediata.
conn.commit()
Tipos de Datos en SQLite

SQLite usa tipado dinámico, pero reconoce estos tipos principales:

  • NULL: Valor nulo.
  • INTEGER: Números enteros.
  • REAL: Números de punto flotante.
  • TEXT: Cadenas de caracteres (UTF-8).
  • BLOB: Datos binarios (imágenes, archivos).

4️⃣ Gestión de Transacciones: Atomicidad​

Este es el concepto más importante de las bases de datos. Una operación debe ser atómica: o se hace entera, o no se hace nada.

🟩 commit() — El punto de no retorno​

Cuando insertas o modificas datos, los cambios se quedan en una "sala de espera" (buffer de transacción). commit() es el botón que los graba definitivamente en el disco.

🟦 with y las transacciones​

En sqlite3, el uso de with sobre el objeto conexión gestiona automáticamente la transacción:

  • Si el bloque termina bien ➡️ hace commit().
  • Si hay un error ➡️ hace rollback() (deshace los cambios).
with sqlite3.connect("banco.db") as conn:
# Si algo falla dentro de este bloque, no se guarda nada
conn.execute("UPDATE cuentas SET saldo = saldo - 100 WHERE id = 1")
conn.execute("UPDATE cuentas SET saldo = saldo + 100 WHERE id = 2")

5️⃣ Configuración Avanzada: Row Factory​

Por defecto, SQLite devuelve los resultados como tuplas. Esto es poco práctico porque tienes que recordar que el nombre es la posición [1].

Podemos hacer que se comporte como un diccionario usando sqlite3.Row:

conexion = sqlite3.connect("datos.db")
conexion.row_factory = sqlite3.Row # ¡Configuración mágica!

cursor = conexion.cursor()
cursor.execute("SELECT * FROM usuarios")
fila = cursor.fetchone()

print(fila["nombre"]) # ¡Mucho más legible que fila[1]!

✅ Buenas prácticas con SQLite​

resumen profesional
  • Usa with: Para gestionar transacciones de forma segura.
  • Cierra siempre: Aunque el with gestione la transacción, el close() libera el archivo del SO.
  • Mayúsculas en SQL: Escribe las palabras reservadas (SELECT, FROM, WHERE) en mayúsculas para diferenciar el código.
  • No insertes variables directamente: Nunca uses f-strings para meter datos en SQL (riesgo de Inyección SQL). Usa marcadores ? (lo veremos en el siguiente punto).

🧪 Ejercicios prácticos – Misión: La Cripta de Datos​

Has sido contratado para digitalizar el inventario de una antigua biblioteca secreta que ha decidido abandonar los pergaminos de texto y pasarse a la persistencia digital.

🟢 Fase 1 – Cimentación del Sistema​

🟩 Ejercicio 1 – La Primera Piedra​

Crea un script que genere una base de datos llamada biblioteca_secreta.db. Si la base de datos ya existe, simplemente debe conectar. Al finalizar, debe imprimir un mensaje confirmando la conexión y cerrar el recurso correctamente.

🟦 Ejercicio 2 – Estructura del Conocimiento​

Define una tabla llamada libros con los siguientes requisitos:

  • id: Entero, clave primaria y autoincremental.
  • titulo: Texto, obligatorio (NOT NULL).
  • autor: Texto, obligatorio.
  • paginas: Entero, debe ser mayor que 0 (CHECK).
  • estado: Texto, por defecto valor 'disponible'.

🟡 Fase 2 – Integridad y Memoria​

🟨 Ejercicio 3 – El Laboratorio de Pruebas​

Crea una base de datos en memoria (:memory:).

  1. Crea la misma tabla libros del ejercicio anterior.
  2. Intenta crear una tabla alquimia con un solo campo id.
  3. Consulta la tabla especial de sistema de SQLite (sqlite_master) para listar todas las tablas creadas y muéstralas por pantalla.

🟧 Ejercicio 4 – Transacción Riesgosa​

Simula el siguiente escenario:

  1. Abre una conexión a biblioteca_secreta.db.
  2. Crea una tabla socios (id, nombre).
  3. Inicia un bloque with (transacción).
  4. Inserta un socio.
  5. Fuerza un error (por ejemplo, dividiendo por cero o lanzando una excepción manual).
  6. Tras el error, verifica si el socio se guardó. Si has usado bien el with, la base de datos debería estar vacía de socios.

🔴 Fase 3 – Configuración Experta​

🟥 Ejercicio 5 – El Bibliotecario Eficiente​

Configura una conexión a biblioteca_secreta.db para que use sqlite3.Row.

  1. Crea una tabla configuracion con los campos clave (TEXT) y valor (TEXT).
  2. Inserta manualmente (usando el cursor) la fila ('idioma', 'español').
  3. Haz un SELECT * y accede al valor de 'idioma' usando la clave del diccionario, no el índice numérico.